package org.example.JVM.Proxy.JDK;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import static org.objectweb.asm.Opcodes.*;

/**
 * @description: 用于动态生成代理类，只要传递需要生成的代理类类名以及代理类需要实现的接口就能获取生成代理类的字节数组
 * @author: farprt
 * @create: 2022-11-18 19:46
 **/
public class MyProxyFactory {

    /**
     * @param className
     * @param interfaces
     * @return: {@link byte[]}
     * @Author: farprt
     * @date: 2022/11/18 19:48
     * @description: 只要传递需要生成的代理类类名以及代理类需要实现的接口就能获取生成代理类的字节数组
     */
    public static byte[] createProxyClass(String className, Class<?>[] interfaces) {
        // 首先用ClassWriter创建一个类，设置类的class文件结构版本号，访问标志，类名，类签名，父类名，实现的接口
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        classWriter.visit(Opcodes.V1_8
                , ACC_PUBLIC
                , className
                , null
                , Type.getInternalName(MyProxy.class)
                , getInternalNames(interfaces));
        // 添加带构造方法，调用父类MyProxy的带参构造方法
        createInitMethod(classWriter);
        // 实现接口方法，不考虑代理toString等Object类方法
        for (Class<?> interfaceClass : interfaces) {
            implInterfaceMethod(classWriter, className, interfaceClass);
        }
        // 添加静态代码块，获取method
        addStaticBlock(classWriter, className, interfaces);
        classWriter.visitEnd();
        return classWriter.toByteArray();
    }

    private static String[] getInternalNames(Class<?>[] interfaces) {
        String[] internalNames = new String[interfaces.length];
        for (int i = 0; i < interfaces.length; i++) {
            internalNames[i] = Type.getInternalName(interfaces[i]);
        }
        return internalNames;
    }

    /**
     * @param classWriter
     * @param className
     * @param interfaceClass
     * @return:
     * @Author: farprt
     * @date: 2022/12/4 20:23
     * @description: 通过反射获取接口声明的方法
     */
    private static void implInterfaceMethod(ClassWriter classWriter, String className, Class<?> interfaceClass) {
        Method[] methods = interfaceClass.getMethods();
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            // 给代理类添加静态字段，使用这个格式生成字段名为了不让字段名重复
            classWriter.visitField(ACC_PRIVATE | ACC_STATIC
                    , "_" + interfaceClass.getSimpleName() + "_" + i
                    , Type.getDescriptor(Method.class)
                    , null
                    , null);
            // 给实现类实现该接口方法
            MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC
                    , method.getName()
                    , Type.getMethodDescriptor(method)
                    , null
                    , new String[]{Type.getInternalName(Exception.class)});
            methodVisitor.visitCode();
            // 获取父类字段，字段名为h，类型为InvocationHandler，然后调用字段h（InvocationHandler）的invoke方法
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitFieldInsn(GETFIELD
                    , Type.getInternalName(MyProxy.class)
                    , "h"
                    , Type.getDescriptor(InvocationHandler.class));
            // 准备调用InvocationHandler的invoke方法的三个参数，放入操作数栈顶
            // 第一个参数this引用放入操作数栈顶
            methodVisitor.visitVarInsn(ALOAD, 0);
            // 第二个参数，获取静态字段，将方法的Method对象放入操作数栈顶
            methodVisitor.visitFieldInsn(GETSTATIC, className, "_" + interfaceClass.getSimpleName() + "_" + i, Type.getDescriptor(Method.class));
            // 第三个参数，将当前方法的参数构造成数组，放入操作数栈顶
            int paramCount = method.getParameterCount();
            if (paramCount == 0) {
                methodVisitor.visitInsn(ACONST_NULL);
            } else {
                // 数组大小
                switch (paramCount) {
                    case 1:
                        methodVisitor.visitInsn(ICONST_1);
                        break;
                    case 2:
                        methodVisitor.visitInsn(ICONST_2);
                        break;
                    case 3:
                        methodVisitor.visitInsn(ICONST_3);
                        break;
                    default:
                        methodVisitor.visitVarInsn(BIPUSH, paramCount);
                }
                //创建数组
                methodVisitor.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class));
                // 为数组元素赋值
                for (int idx = 1; idx <= paramCount; idx++) {
                    methodVisitor.visitInsn(DUP);
                    switch (idx - 1) {
                        case 0:
                            methodVisitor.visitInsn(ICONST_0);
                            break;
                        case 1:
                            methodVisitor.visitInsn(ICONST_1);
                            break;
                        case 2:
                            methodVisitor.visitInsn(ICONST_2);
                            break;
                        case 3:
                            methodVisitor.visitInsn(ICONST_3);
                            break;
                        default:
                            methodVisitor.visitVarInsn(BIPUSH, i - 1);
                            break;
                    }
                    // 暂不考虑参数类型为基本数据类型
                    methodVisitor.visitVarInsn(ALOAD, idx);
                    methodVisitor.visitInsn(AASTORE);
                }
            }
            //调用InvocationHandler的invoke方法
            methodVisitor.visitMethodInsn(INVOKEINTERFACE
                    , Type.getInternalName(InvocationHandler.class)
                    , "invoke"
                    , "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"
                    , true);
            addReturnInstruc(methodVisitor, method.getReturnType());
            methodVisitor.visitFrame(F_FULL, 0, null, 0, null);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();

        }
    }

    /**
     * @param methodVisitor
     * @param returnType
     * @return:
     * @Author: farprt
     * @date: 2022/12/4 21:21
     * @description: 为实现的方法添加一条返回指令
     */
    private static void addReturnInstruc(MethodVisitor methodVisitor, Class<?> returnType) {
        if (returnType == void.class) {
            methodVisitor.visitInsn(RETURN);
        } else if (returnType == int.class) {
            // 先将InvocationHandler的invoke方法返回值由Object类转为Integer类
            methodVisitor.visitTypeInsn(CHECKCAST, Type.getInternalName(Integer.class));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", "()I", false);
            methodVisitor.visitInsn(IRETURN);
        }
        //TODO 所有基本数据类型
        else {
            // 返回为引用类型是，使用areturn指令，在此之前加类型检测指令，实现强制类型转换，因为InvocationHandler的invoke方法返回值为Object
            methodVisitor.visitTypeInsn(CHECKCAST, Type.getInternalName(returnType));
            methodVisitor.visitInsn(ARETURN);
        }
    }


    /**
     * @param classWriter
     * @return:
     * @Author: farprt
     * @date: 2022/12/6 20:07
     * @description: init：是对象构造器方法，也就是new一个对象才会执行init方法
     * clinit：是类构造器方法，jvm进行类加载-验证-解析-初始化，初始化时jvm会调用clinit方法
     * <p>
     * init和clinit执行目的不同：
     * init：实例构造器，对非静态变量解析初始化
     * clinit：类构造器，对静态变量和静态代码块进行初始化
     * <p>
     * 作者：G_Freedom
     * 链接：https://www.jianshu.com/p/fd0133c4746d
     * 来源：简书
     * 著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
     */
    private static void createInitMethod(ClassWriter classWriter) {
        // 为代理类添加一个带参且参数类型为InvocationHandler的构造方法
        MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC
                , "<init>"
                , "(Ljava/lang/reflect/InvocationHandler;)V"
                , null
                , null);
        // 调用父类构造方法
        methodVisitor.visitMethodInsn(INVOKESPECIAL
                , Type.getInternalName(MyProxy.class)
                , "<init>"
                , "(Ljava/lang/reflect/InvocationHandler;)V"
                , false);
        methodVisitor.visitInsn(RETURN);
        methodVisitor.visitMaxs(2, 2);
        methodVisitor.visitEnd();
    }

    /**
     * @param classWriter
     * @param className
     * @param interfaces
     * @return:
     * @Author: farprt
     * @date: 2022/12/4 21:27
     * @description: 为代理类生成静态代码块，在静态代码块中为静态字段赋值
     */
    private static void addStaticBlock(ClassWriter classWriter, String className, Class<?>[] interfaces) {
        // 给<clinit>添加static访问标志，对应java代码静态代码块
        MethodVisitor mv = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
        mv.visitCode();
        // 遍历所有接口
        for (Class cla : interfaces) {
            // 遍历接口的所有方法
            Method[] methods = cla.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                // 调用class的forName方法获取一个Class实例
                String fieldName = "_" + cla.getSimpleName() + "_" + i;
                mv.visitLdcInsn(cla.getName());
                mv.visitMethodInsn(INVOKESTATIC,
                        Type.getInternalName(Class.class),
                        "forName",
                        "(Ljava/lang/String;)Ljava/lang/Class;",
                        false);
                // 调用Class的getMethod方法，需要两个参数：方法名和方法参数类型数组
                mv.visitLdcInsn(method.getName());
                Class[] methodParamTypes = method.getParameterTypes();
                if (methodParamTypes.length == 0) {
                    mv.visitInsn(ACONST_NULL);
                } else {
                    switch (methodParamTypes.length) {
                        case 1:
                            mv.visitInsn(ICONST_1);
                            break;
                        case 2:
                            mv.visitInsn(ICONST_2);
                            break;
                        case 3:
                            mv.visitInsn(ICONST_3);
                            break;
                        default:
                            mv.visitVarInsn(BIPUSH, methodParamTypes.length);
                    }
                    // 已经在implInterfaceMethod方法中为代理类持有的各接口方法Method对象的引用添加对应的静态字段
                    // 因此只需要为这些静态字段赋值
                    mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Class.class));
                    // 为数组元素赋值，类型为Class
                    for (int idx = 0; idx < methodParamTypes.length; idx++) {
                        mv.visitInsn(DUP);
                        // 数组元素下表
                        switch (idx) {
                            case 0:
                                mv.visitInsn(ICONST_0);
                                break;
                            case 1:
                                mv.visitInsn(ICONST_1);
                                break;
                            case 2:
                                mv.visitInsn(ICONST_2);
                                break;
                            case 3:
                                mv.visitInsn(ICONST_3);
                                break;
                            default:
                                mv.visitVarInsn(BIPUSH, i);
                                break;
                        }
                        mv.visitLdcInsn(methodParamTypes[idx].getName());
                        // 调用forName获取参数的Class实例
                        mv.visitMethodInsn(INVOKESTATIC,
                                Type.getInternalName(Class.class),
                                "forName",
                                "(Ljava/lang/String;)Ljava/lang/Class;",
                                false);
                        //存储到数组
                        mv.visitInsn(AASTORE);
                    }
                    // 参数准备完毕，调用Class的getMethod方法获取Method
                    mv.visitMethodInsn(INVOKEVIRTUAL,
                            Type.getInternalName(Class.class),
                            "getMethod",
                            "(Ljava/lang/Class;)Ljava/lang/reflect/Method;",
                            false);
                    // 为静态字段赋值
                    mv.visitFieldInsn(PUTSTATIC, className, fieldName, Type.getDescriptor(Method.class));
                }
            }
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }

    }

}